package manager

import (
	"encoding/base64"
	"fmt"
	conf "gmap-backend/internal/config"
	"gmap-backend/internal/util/commonutil"
	"gmap-backend/internal/util/htmlutil"
	"gmap-backend/internal/util/httpurlutil"
	"gmap-backend/internal/util/httputil"
	"gmap-backend/internal/util/imgutil"
	"golang.org/x/text/encoding/simplifiedchinese"
	"log"
	"os"
	"regexp"
	"strings"
	"syscall"
	"unsafe"
)

const DEBUG_FAVICON = true

func prtf(pattern string, args ...any) {
	if DEBUG_FAVICON {
		if 1 == len(args) {
			fmt.Printf(pattern+"\n", args[0])
		} else if 2 == len(args) {
			fmt.Printf(pattern+"\n", args[0], args[1])
		} else if 3 == len(args) {
			fmt.Printf(pattern+"\n", args[0], args[1], args[2])
		} else if 4 == len(args) {
			fmt.Printf(pattern+"\n", args[0], args[1], args[2], args[3])
		} else if 5 == len(args) {
			fmt.Printf(pattern+"\n", args[0], args[1], args[2], args[3], args[4])
		}
	}
}

func prt(args ...any) {
	if DEBUG_FAVICON {
		if 1 == len(args) {
			prtf("%v", args[0])
		} else if 2 == len(args) {
			prtf("%v %v", args[0], args[1])
		} else if 3 == len(args) {
			prtf("%v %v %v", args[0], args[1], args[2])
		} else if 4 == len(args) {
			prtf("%v %v %v %v", args[0], args[1], args[2], args[3])
		} else if 5 == len(args) {
			prtf("%v %v %v %v %v", args[0], args[1], args[2], args[3], args[4])
		}
	}
}

var base64imgReg = regexp.MustCompile("^data[:]image[/](.+?);base64,(.+?)$")

// 加载指定url的图标
func LoadFavicon(config *conf.AppConfig, url string) (resultSucc bool, resultSavePath string) {
	defer func() {
		if e := recover(); nil != e {
			log.Println("load favicon err for url", url)
			resultSucc = false
			resultSavePath = ""
		}
	}()

	// 初始保存路径不包含扩展名，等确认图片格式后再rename到新的文件名
	initSavePath := fmt.Sprintf(`%s\%s`, config.PathConfig.WorkDir, commonutil.GetUuid())
	cache := map[string]*httputil.HtmlContResult{}
	handlers := []func(*conf.AppConfig, string, string, map[string]*httputil.HtmlContResult) (bool, string){
		attempGetFaviconByDirectImgUrl,
		attempGetFaviconByDirectJoinUrl,
		attempGetFaviconByLinkTag,
		attempGetFaviconByMetaTagRefresh,
		attempGetFaviconByScriptLocationHref,
	}
	for _, handler := range handlers {
		valid, savePath := handler(config, url, initSavePath, cache)
		if valid {
			log.Println("load favicon succ for url", url, savePath)
			return true, savePath
		}
	}
	log.Println("load favicon fail for url", url)
	return false, ""
}

// ensureImgFileValid 判断指定路径文件是否是有效的图片，如果是，则增加对应的扩展名，若为ico，则再保存为png格式
func ensureImgFileValid(config *conf.AppConfig, savePath string) (bool, string) {
	valid, imgType := imgutil.IsValidImgFile(savePath)
	if !valid {
		return false, ""
	}
	newSavePath := savePath + "." + imgType
	os.Rename(savePath, newSavePath)
	if "ico" != imgType {
		return true, newSavePath
	}

	// 如果是ico文件，则尝试转化为png返回，如果失败仍返回原来的ico类型
	pngSavePath := savePath + ".png"
	from, _ := syscall.UTF16PtrFromString(newSavePath)
	to, _ := syscall.UTF16PtrFromString(pngSavePath)
	rs, _, _ := config.Win32LibConfig.LoadIconProc.Call(uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(to)))
	if 0 == rs {
		valid, _ = imgutil.IsValidImgFile(pngSavePath)
		if valid {
			return true, pngSavePath
		}
	}
	return true, newSavePath
}

// attempGetFaviconByDirectImgUrl 假设给定的url本身就是图片地址，直接尝试下载并验证是否为有效图片文件
func attempGetFaviconByDirectImgUrl(config *conf.AppConfig, url, initSavePath string, cache map[string]*httputil.HtmlContResult) (bool, string) {
	downRes := httputil.Download(url, initSavePath, conf.DEFAULT_REQ_TIMEOUT, conf.DEFAULT_UA)
	if !downRes.Succ {
		return false, ""
	}
	succ, path := ensureImgFileValid(config, initSavePath)
	if succ {
		return true, path
	}

	// 如果下载后发现不是有效图片，则把下载内容缓存，以便后续处理能使用到
	if strings.HasPrefix(downRes.ContentType, "text/") {
		bs, err := os.ReadFile(initSavePath)
		if nil != err {
			panic(err)
		}
		if downRes.CnEncode {
			bs, err = simplifiedchinese.GBK.NewDecoder().Bytes(bs)
			if nil != err {
				panic(err)
			}
		}
		cache[url] = &httputil.HtmlContResult{
			Succ:    true,
			Content: string(bs),
			FactUrl: downRes.FactUrl,
		}
	}
	return false, path

}

// attempGetFaviconByDirectJoinUrl 尝试通过url拼接获得图标，如果是重定向，则把重定向后的地址再次尝试拼接favicon.ico后获取图标
// https://baidu.com/a/b/c -> https://baidu.com/favicon.ico
func attempGetFaviconByDirectJoinUrl(config *conf.AppConfig, url, initSavePath string, cache map[string]*httputil.HtmlContResult) (bool, string) {
	rootUrl := httpurlutil.GetRootUrl(url)
	attempUrl := fmt.Sprintf("%s/favicon.ico", rootUrl)
	downRes := httputil.Download(attempUrl, initSavePath, conf.DEFAULT_REQ_TIMEOUT, conf.DEFAULT_UA)
	if !downRes.Succ {
		return false, ""
	}
	succ, newSavePath := ensureImgFileValid(config, initSavePath)
	if succ {
		return true, newSavePath
	}

	// 如果是重定向，则把重定向后的地址再次尝试拼接favicon.ico后获取图标
	rootUrl2 := httpurlutil.GetRootUrl(downRes.FactUrl)
	if rootUrl2 == rootUrl {
		return false, ""
	}
	attempUrl = fmt.Sprintf("%s/favicon.ico", rootUrl2)
	downRes = httputil.Download(attempUrl, initSavePath, conf.DEFAULT_REQ_TIMEOUT, conf.DEFAULT_UA)
	if !downRes.Succ {
		return false, ""
	}
	return ensureImgFileValid(config, initSavePath)
}

// attempGetFaviconByLinkTag 尝试通过link标签获取图标
// <link ref="**icon**" href="图标地址"/>
func attempGetFaviconByLinkTag(config *conf.AppConfig, url, initSavePath string, cache map[string]*httputil.HtmlContResult) (bool, string) {
	var html *httputil.HtmlContResult = nil
	if cont, ok := cache[url]; ok {
		prt("tag url cached", url)
		html = cont
	} else {
		html = httputil.GetHtmlContentWithRedirect(url, conf.DEFAULT_REQ_TIMEOUT, conf.DEFAULT_UA)
		if !html.Succ {
			return false, ""
		}
		cache[url] = html
	}

	subUrl := strings.TrimSpace(htmlutil.GetFavIconUrlByLinkTag(html.Content))
	prt("url in tag", subUrl)
	if "" == subUrl {
		return false, ""
	}

	// 地址是base64图片格式
	matches := base64imgReg.FindStringSubmatch(subUrl)
	if len(matches) >= 3 {
		bs, err := base64.StdEncoding.DecodeString(strings.TrimSpace(matches[2]))
		commonutil.ThrowIfNeed(err)
		os.WriteFile(initSavePath, bs, 0666)
		return ensureImgFileValid(config, initSavePath)
	}

	// 否则把原地址与子地址适当拼接后获取
	iconUrl := httpurlutil.CombineUrl(html.FactUrl, httpurlutil.GetRootUrl(html.FactUrl), subUrl)
	prt("combined url", iconUrl)
	prt("icon url", iconUrl)
	downRes := httputil.Download(iconUrl, initSavePath, conf.DEFAULT_REQ_TIMEOUT, conf.DEFAULT_UA)
	if !downRes.Succ {
		return false, ""
	}
	return ensureImgFileValid(config, initSavePath)
}

// attempGetFaviconByMetaTagRefresh 尝试通过mata标签上的refresh地址获取图标
func attempGetFaviconByMetaTagRefresh(config *conf.AppConfig, url, initSavePath string, cache map[string]*httputil.HtmlContResult) (bool, string) {
	var html *httputil.HtmlContResult = nil
	if cont, ok := cache[url]; ok {
		prt("meta url cached ", url)
		html = cont
	} else {
		html = httputil.GetHtmlContentWithRedirect(url, conf.DEFAULT_REQ_TIMEOUT, conf.DEFAULT_UA)
		if !html.Succ {
			return false, ""
		}
		cache[url] = html
	}

	refreshUrl := htmlutil.GetMetaRefreshUrl(html.Content)
	if "" == refreshUrl {
		return false, ""
	}
	refreshUrl = httpurlutil.CombineUrl(html.FactUrl, httpurlutil.GetRootUrl(html.FactUrl), refreshUrl)

	succ, savePath := attempGetFaviconByDirectJoinUrl(config, refreshUrl, initSavePath, cache)
	if succ {
		return true, savePath
	}
	return attempGetFaviconByLinkTag(config, refreshUrl, initSavePath, cache)
}

// attempGetFaviconByScriptLocationHref 尝试通过 location.href=""; 这样的语句获取跳转地址并从该地址获取图标
func attempGetFaviconByScriptLocationHref(config *conf.AppConfig, url, initSavePath string, cache map[string]*httputil.HtmlContResult) (bool, string) {
	var html *httputil.HtmlContResult = nil
	if cont, ok := cache[url]; ok {
		prt("script url cached", url)
		html = cont
	} else {
		html = httputil.GetHtmlContentWithRedirect(url, conf.DEFAULT_REQ_TIMEOUT, conf.DEFAULT_UA)
		if !html.Succ {
			return false, ""
		}
		cache[url] = html
	}

	href := htmlutil.GetScriptLocationHref(html.Content)
	if "" == href {
		return false, ""
	}
	href = httpurlutil.CombineUrl(html.FactUrl, httpurlutil.GetRootUrl(html.FactUrl), href)

	succ, savePath := attempGetFaviconByDirectJoinUrl(config, href, initSavePath, cache)
	if succ {
		return true, savePath
	}
	return attempGetFaviconByLinkTag(config, href, initSavePath, cache)
}
